# coding: utf-8
from minepy import MINE
import pandas as pd
import numpy as np
import scipy
from ..external.iv import WOE


def conc_vs_conc_group_rate(x, y, df):
    '''
    计算两个离散型变量，分组后的比率
    要求不能有nan
    '''
    assert df[x].notnull().all()
    assert df[y].notnull().all()
    x_values = df[x].unique()
    y_values = df[y].unique()
    x_values_dict = {v: i for i, v in enumerate(x_values)}
    y_values_dict = {v: i for i, v in enumerate(y_values)}
    result = np.zeros((len(x_values), len(y_values)))

    for row in df.itertuples():
        result[
            x_values_dict[getattr(row, x)],
            y_values_dict[getattr(row, y)]
        ] += 1

    result_sum = result.sum(axis=1)
    for i in range(result.shape[1]):
        result[:, i] = result[:, i] / result_sum

    return pd.DataFrame(result,
                        index=pd.Index(x_values, name=x),
                        columns=pd.Index(y_values, name=y)
                        )


def pearsonr(x, y, df):
    '''
    计算两个连续型变量的pearson相关系数
    要求不能有nan
    '''
    assert df[x].notnull().all()
    assert df[y].notnull().all()
    return scipy.stats.pearsonr(df[x], df[y])[0]


def pearsonr_sort(y, df):
    '''
    y为预测目标列，计算剩下的列和y的pearson相关系数， 并按pearson相关系数的绝对值从大到小排列。
    要求不能有nan，列名不能有重复, 并且都是连续型的。
    '''
    assert (df.columns.duplicated() == False).all()
    assert df.notnull().all().all()
    index = []
    pearson = []
    for i in range(len(df.columns)):
        if df.columns[i] != y:
            index.append(df.columns[i])
            col_i = df.ix[:, i:i + 1]
            col_y = df[y]
            pearson.append(scipy.stats.pearsonr(col_i.values.reshape(-1), col_y.values)[0])

    result = pd.DataFrame(pearson, index=index, columns=['pearson'])
    result['abs_pearson'] = np.abs(result['pearson'])
    result = result.sort_values('abs_pearson', ascending=False)
    return result


def mic(x, y, df):
    '''
    计算两个连续型变量的mic
    要求不能有nan
    '''
    assert df[x].notnull().all()
    assert df[y].notnull().all()
    mine = MINE(alpha=0.6, c=15)
    mine.compute_score(df[x].values.reshape(-1), df[y].values.reshape(-1))
    return mine.mic()


def mic_sort(y, df):
    '''
    y为预测目标列，计算剩下的列和y的mic， 并按mic从大到小排列。
    要求不能有nan，列名不能有重复, 并且都是连续型的。
    '''
    assert (df.columns.duplicated() == False).all()
    assert df.notnull().all().all()
    index = []
    mic = []
    mine = MINE(alpha=0.6, c=15)
    for i in range(len(df.columns)):
        if df.columns[i] != y:
            index.append(df.columns[i])
            col_i = df.ix[:, i:i + 1]
            col_y = df[y]
            mine.compute_score(col_i.values.reshape(-1), col_y.values.reshape(-1))
            mic.append(mine.mic())

    result = pd.DataFrame(mic, index=index, columns=['mic'])
    result = result.sort_values('mic', ascending=False)
    return result


def all_pair_pearsonr(df):
    '''
    两两计算一个df中的列之间pearson相关系数。结果按绝对值从大到小排序
    要求不能有nan
    '''
    assert df.notnull().all().all()
    index = []
    pearson = []
    for i in range(len(df.columns) - 1):
        for j in range(i + 1, len(df.columns)):
            '''pearson 系数具有对称性，我们只计算n(n-1)/2次'''
            col_i = df.ix[:, i:i + 1]
            col_j = df.ix[:, j:j + 1]
            p_r = scipy.stats.pearsonr(col_i.values, col_j.values)[0]
            if isinstance(p_r, np.ndarray):
                p_r = p_r[0]
            pearson.append(p_r)
            index.append((df.columns[i], df.columns[j]))
    result = pd.DataFrame(pearson, index=pd.MultiIndex.from_tuples(index), columns=['pearson'])
    result['abs_pearson'] = np.abs(result['pearson'])
    result = result.sort_values('abs_pearson', ascending=False)
    return result


def group_vs_pair_pearsonr(df1, df2):
    '''
    有两组df，一个是df1，一个是df2，我们不计算它们内部的pearsonr，而是计算所有交叉的pearsonr。
    用于比较新加入的特征和之前特征的关系。
    要求不能有nan
    '''
    assert df1.notnull().all().all()
    assert df2.notnull().all().all()
    index = []
    pearson = []
    for i in range(len(df1.columns)):
        for j in range(len(df2.columns)):
            col_i = df1.ix[:, i:i + 1]
            col_j = df2.ix[:, j:j + 1]
            pearson.append(scipy.stats.pearsonr(col_i.values, col_j.values)[0])
            index.append((df1.columns[i], df2.columns[j]))
    result = pd.DataFrame(pearson, index=pd.MultiIndex.from_tuples(index), columns=['pearson'])
    result['abs_pearson'] = np.abs(result['pearson'])
    result = result.sort_values('abs_pearson', ascending=False)
    return result


def all_pair_mic(df):
    '''
    两两计算一个df中的列之间mic。结果按绝对值从大到小排序
    要求不能有nan
    '''
    assert df.notnull().all().all()
    index = []
    mic = []
    mine = MINE(alpha=0.6, c=15)
    for i in range(len(df.columns) - 1):
        for j in range(i + 1, len(df.columns)):
            '''mic 系数具有对称性，我们只计算n(n-1)/2次'''
            col_i = df.ix[:, i:i + 1]
            col_j = df.ix[:, j:j + 1]
            mine.compute_score(col_i.values.reshape(-1), col_j.values.reshape(-1))
            mic.append(mine.mic())
            index.append((df.columns[i], df.columns[j]))
    result = pd.DataFrame(mic, index=pd.MultiIndex.from_tuples(index), columns=['mic'])
    result = result.sort_values('mic', ascending=False)
    return result


def information_value(x, y, positive_y_value, df):
    '''
    对离散特征x，离散目标y，计算信息量，要求y必须为二类。
    positive_y_value表示y中哪一类被考虑为正类
    返回：woe_dict, iv
    woe_dict是组内value -> woe的dict。
    woe为0代表无影响，>0代表有positive_y_value一类的影响，<0代表有非positive_y_value一类的影响。绝对值越大影响越大。
    iv就是需要的information_value
    '''
    positive_y_value = str(positive_y_value)
    assert df[x].notnull().all()
    assert df[y].notnull().all()
    w = WOE()
    return w.woe_single_x(df[x].values.reshape(-1).astype(str), df[y].values.reshape(-1).astype(str), positive_y_value)


def information_value_sort(y, positive_y_value, df):
    '''
    y为预测目标列，计算剩下的列和y的iv， 并按iv从大到小排列。
    要求不能有nan，列名不能有重复, 并且都是离散的。
    '''
    positive_y_value = str(positive_y_value)
    assert (df.columns.duplicated() == False).all()
    assert df.notnull().all().all()
    index = []
    iv = []
    w = WOE()
    for i in range(len(df.columns)):
        if df.columns[i] != y:
            index.append(df.columns[i])
            col_i = df.ix[:, i:i + 1]
            col_y = df[y]
            iv.append(w.woe_single_x(col_i.values.reshape(-1).astype(str),
                                     col_y.values.reshape(-1).astype(str),
                                     positive_y_value
                                     )[1]
                      )
    result = pd.DataFrame(iv, index=index, columns=['iv'])
    result = result.sort_values('iv', ascending=False)
    return result


def col_std(df, resize=True):
    '''
    计算每一列的标准差。
    resize=True时。先均匀放缩到0~1，再做标准差的计算。（感觉这个更科学）
    resize=False时。不放缩直接计算。
    注意resize=True时。事实上是对0，1这种离散型变量更有利的。因为它们刚好分开在两端。
    所以建议离散型特征和连续型特征分开调用这个函数来处理比较好
    做完之后可以用sort_scatter来看那一列的散点
    '''

    if resize:
        df = df.copy()
        for col in df.columns:
            max_value = df[col].max()
            min_value = df[col].min()
            if max_value == min_value:
                print('warning: column: %s max=min' % (col))
            else:
                df[col] = (df[col] - min_value) / (max_value - min_value)

    col_std = df.std(axis=0)
    if resize:
        col_std = pd.DataFrame(col_std, columns=['resized_std'])
        return col_std.sort_values('resized_std', ascending=True)
    else:
        col_std = pd.DataFrame(col_std, columns=['std'])
        return col_std.sort_values('std', ascending=True)
